﻿using LambdaToSql.FrameWork;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace LambdaToSql.Realization
{
    internal partial class Queryable<T> : LambdaToSql.Interface.IQueryable<T> where T : class, new()
    {
        #region 参数 构造函数

        internal virtual LambdaToSql.EntityModel.QueryEntity<T> Entity { get; set; }

        public virtual LambdaToSql.EntityModel.DbContext Context { get; set; }

        public virtual LambdaToSql.EntityModel.Result<IList<T>> Debug { get; private set; }

        public Queryable(LambdaToSql.EntityModel.DbContext context)
        {
            this.Context = context;
            //Entity = new LambdaToSql.EntityModel.QueryEntity<T>(this.Context);

            string assemblyName = "LambdaToSql";
            string fullTypeName = string.Format("LambdaToSql.EntityModel.{0}.{1}`1", this.Context.SqlType.ToString(), "QueryEntity");
            Type objType = Assembly.Load(assemblyName).GetType(fullTypeName);
            Type type = objType.MakeGenericType(typeof(T));
            Entity = (LambdaToSql.EntityModel.QueryEntity<T>)Activator.CreateInstance(type, new object[] { this.Context });
        }
        #endregion

        #region Any

        public virtual bool Any()
        {
            var count = this.Count();
            if (count > 0)
            {
                return true;
            }
            return false;
        }

        public virtual bool Any(Expression<Func<T, bool>> expression)
        {
            if (expression != null)
            {
                var count = this.Count();
                if (count > 0)
                {
                    return true;
                }
                return false;
            }
            return false;
        }
        #endregion

        #region Select

        public virtual LambdaToSql.Interface.IQueryable<T> Select(Expression<Func<T, object>> expression)
        {
            if (expression != null)
            {
                var obj = new LambdaRouter(this.Entity.Parameters);
                string str = obj.ExpressionRouter(expression);
                this.Entity.Fields.AddRange(str.Split(',').ToList());
            }
            return this;
        }
        #endregion

        #region Where

        public virtual LambdaToSql.Interface.IQueryable<T> Where(Expression<Func<T, bool>> expression)
        {
            string result = string.Empty;
            if (expression != null)
            {
                Expression exp = expression.Body as Expression;

                var obj = new LambdaRouter(this.Entity.Parameters);
                var str = obj.ExpressionRouter(exp);
                this.Entity.Parameters = obj.Parameters;
                if (this.Entity.Where.Length > 0)
                {
                    this.Entity.Where.Append(" and ");
                }
                this.Entity.Where.Append(obj.Result);
            }
            return this;
        }
        #endregion

        #region Skip Take

        public virtual LambdaToSql.Interface.IQueryable<T> Skip(int index)
        {
            this.Entity.Index = index;
            return this;
        }

        public virtual LambdaToSql.Interface.IQueryable<T> Take(int num)
        {
            this.Entity.Top = num;
            return this;
        }
        #endregion

        #region GroupBy OrderBy OrderByDescending

        public virtual LambdaToSql.Interface.IQueryable<T> GroupBy(Expression<Func<T, object>> expression)
        {
            if (expression != null)
            {
                var obj = new LambdaRouter();
                var str = obj.ExpressionRouter(expression);
                this.Entity.GroupBy.AddRange(str.Split(',').ToList());
            }
            return this;
        }

        public virtual LambdaToSql.Interface.IQueryable<T> OrderBy<T1>(Expression<Func<T, T1>> expression)
        {
            Order(expression);
            return this;
        }

        public virtual LambdaToSql.Interface.IQueryable<T> OrderByDescending<T1>(Expression<Func<T, T1>> expression)
        {
            Order(expression, "desc");
            return this;
        }

        private LambdaToSql.Interface.IQueryable<T> Order<T1>(Expression<Func<T, T1>> expression, string sort = "asc")
        {
            if (expression != null)
            {
                var obj = new LambdaRouter();
                var str = obj.ExpressionRouter(expression);
                this.Entity.OrderBy.AddRange(str.Split(','));

                var index = this.Entity.OrderBy.Count - 1;
                this.Entity.OrderBy[index] += " " + sort;
            }
            return this;
        }


        public virtual LambdaToSql.Interface.IQueryable<T> OrderBy(string name)
        {
            Order(name);
            return this;
        }

        public virtual LambdaToSql.Interface.IQueryable<T> OrderByDescending(string name)
        {
            Order(name, "desc");
            return this;
        }
        private LambdaToSql.Interface.IQueryable<T> Order(string name, string sort = "asc")
        {
            if (!string.IsNullOrEmpty(name))
            {
                this.Entity.OrderBy.Add(name);

                var index = this.Entity.OrderBy.Count - 1;
                this.Entity.OrderBy[index] += " " + sort;
            }
            return this;
        }
        #endregion

        #region Avg Max Min Sum Count

        public virtual Double Avg(Expression<Func<T, object>> expression)
        {
            var DbHelper = this.Context.DbHelper;

            var obj = new LambdaRouter();
            var name = obj.ExpressionRouter(expression);
            this.Entity.Fields = new List<string>() { name + " AS A1" };

            var sql = string.Format(this.Entity.AvgTemplate, "A1");
            var num = DbHelper.ExecuteScalar(sql, this.Entity.Parameters.ToArray());

            var result = Convert.ToDouble(num);
            return result;
        }

        public virtual TResult Max<TResult>(Expression<Func<T, TResult>> expression)
        {
            var DbHelper = this.Context.DbHelper;

            var obj = new LambdaRouter();
            var name = obj.ExpressionRouter(expression);
            this.Entity.Fields = new List<string>() { name + " AS A1" };

            var sql = string.Format(this.Entity.MaxTemplate, "A1");
            var num = DbHelper.ExecuteScalar(sql, this.Entity.Parameters.ToArray());

            var result = (TResult)Convert.ChangeType(num, typeof(TResult));
            return result;
        }

        public virtual TResult Min<TResult>(Expression<Func<T, TResult>> expression)
        {
            var DbHelper = this.Context.DbHelper;

            var obj = new LambdaRouter();
            var name = obj.ExpressionRouter(expression);
            this.Entity.Fields = new List<string>() { name + " AS A1" };

            var sql = string.Format(this.Entity.MinTemplate, "A1");
            var num = DbHelper.ExecuteScalar(sql, this.Entity.Parameters.ToArray());

            var result = (TResult)Convert.ChangeType(num, typeof(TResult));
            return result;
        }

        public virtual Decimal Sum<TResult>(Expression<Func<T, TResult>> expression)
        {
            var DbHelper = this.Context.DbHelper;

            var obj = new LambdaRouter();
            var name = obj.ExpressionRouter(expression);
            this.Entity.Fields = new List<string>() { name + " AS A1" };

            var sql = string.Format(this.Entity.SumTemplate, "A1");
            var num = DbHelper.ExecuteScalar(sql, this.Entity.Parameters.ToArray());

            var result = Convert.ToDecimal(num);
            return result;
        }

        public virtual int Count()
        {
            var DbHelper = this.Context.DbHelper;

            var sql = this.Entity.CountTemplate;
            var i = DbHelper.ExecuteScalar(sql, this.Entity.Parameters.ToArray());
            return Convert.ToInt32(i);
        }
        #endregion

        #region Find First FirstOrDefault

        //public virtual T Find(Guid guid)
        //{
        //    var tableName = this.Entity.TableName;
        //    var primaryName = this.Context.Table[tableName].PrimaryName;
        //    var parameter = Expression.Parameter(typeof(T), "ex");

        //    MemberExpression left = Expression.PropertyOrField(parameter, primaryName);
        //    ConstantExpression right = Expression.Constant(guid, typeof(Guid));//创建常数
        //    var query = Expression.Equal(left, right);

        //    var where = Expression.Lambda<Func<T, bool>>(query, parameter);
        //    return this.Where(where).ToList().FirstOrDefault();
        //}

        //public virtual T Find(int id)
        //{
        //    var tableName = this.Entity.TableName;
        //    var primaryName = this.Context.Table[tableName].PrimaryName;
        //    var parameter = Expression.Parameter(typeof(T), "ex");

        //    MemberExpression left = Expression.PropertyOrField(parameter, primaryName);
        //    ConstantExpression right = Expression.Constant(id, typeof(int?));//创建常数
        //    var query = Expression.Equal(left, right);

        //    var where = Expression.Lambda<Func<T, bool>>(query, parameter);
        //    return this.Where(where).ToList().FirstOrDefault();
        //}

        public virtual T Find(object KeyValue)
        {
            var tableName = this.Entity.TableName;
            var primaryName = this.Context.Table[tableName].PrimaryName;

            var type = typeof(T).GetProperties().Where(ex => ex.Name == primaryName).FirstOrDefault().PropertyType;
            var parameter = Expression.Parameter(this.Entity.TType, "ex");

            MemberExpression left = Expression.PropertyOrField(parameter, primaryName);
            ConstantExpression right = Expression.Constant(KeyValue, type);//创建常数
            var query = Expression.Equal(left, right);

            var where = Expression.Lambda<Func<T, bool>>(query, parameter);
            return this.Where(where).ToList().FirstOrDefault();
        }

        public virtual T First()
        {
            return this.FirstOrDefault();
        }
        public virtual LambdaToSql.EntityModel.Result<IList<T>> First(bool debug)
        {
            return this.FirstOrDefault(true);
        }

        public virtual T FirstOrDefault()
        {
            this.Take(1);
            return this.ToList().FirstOrDefault();
        }
        public virtual LambdaToSql.EntityModel.Result<IList<T>> FirstOrDefault(bool debug)
        {
            this.Take(1);
            return this.ToList(true);
        }
        #endregion

        #region Join

        public virtual LambdaToSql.Interface.IQueryable<T> InnerJoin<T1>(Expression<Func<T1, object>> expression)
        {
            string result = string.Empty;
            if (expression != null)
            {
                Expression exp = expression.Body as Expression;

                var obj = new LambdaRouter(this.Entity.Parameters);
                var str = obj.ExpressionRouter(exp);
                this.Entity.Parameters = obj.Parameters;
                if (this.Entity.Where.Length > 0)
                {
                    this.Entity.Where.Append(" and ");
                }
                this.Entity.Where.Append(obj.Result);
            }
            return this;
        }
        #endregion

        #region Pages

        public virtual IList<T> ToPageList(ref int total)
        {
            if (this.Entity.Index == null)
            {
                this.Entity.Index = 1;
            }
            if (this.Entity.Top == null)
            {
                this.Entity.Top = 15;
            }
            return ToPageList(this.Entity.Index.Value, this.Entity.Top.Value, ref total);
        }

        public virtual IList<T> ToPageList(int pageIndex, int pageSize, ref int total)
        {
            var DbHelper = this.Context.DbHelper;
            var version = DbHelper.ExecuteScalar("SELECT @@VERSION AS Expr1");
            
            var sql = "";
            if (version.StartsWith("Microsoft SQL Server 2005") || version.StartsWith("Microsoft SQL Server 2008"))
            {
                var start = (pageIndex - 1) * pageSize + 1;
                var end = pageIndex * pageSize;
                sql = string.Format(this.Entity.PageTemplate_2008_2005, start, end);
            }
            else
            {
                var start = (pageIndex - 1) * pageSize; 
                sql = string.Format(this.Entity.PageTemplate, start, pageSize);
            }

            var totalSql = string.Format("select Count(*) from {0} {1}", this.Entity.TableName, this.Entity.WhereStr);
            total = Convert.ToInt32(DbHelper.ExecuteScalar(totalSql, this.Entity.Parameters.ToArray()));

            var sdr = DbHelper.ExecuteReader(sql, this.Entity.Parameters.ToArray());
            var list = Tools.IDataReaderToIlist<T>(sdr);
            return list;
        }

        #endregion

        #region ToList

        public virtual IList<T> ToList()
        {
            if (this.Entity.Index != null || this.Entity.Top != null)//走分页处理
            {
                if (this.Entity.Index == null)
                {
                    this.Entity.Index = 1;
                }
                if (this.Entity.Top == null)
                {
                    this.Entity.Top = 15;
                }
                var total = 0;
                return ToPageList(this.Entity.Index.Value, this.Entity.Top.Value, ref total);
            }
            else
            {
                var obj = ToList(true).Data;
                return obj;
            }
        }

        public virtual LambdaToSql.EntityModel.Result<IList<T>> ToList(bool debug = false)
        {
            var sw = new Stopwatch();
            sw.Start();

            var DbHelper = this.Context.DbHelper;
             
            var sdr = DbHelper.ExecuteReader(this.Entity.SqlStr, this.Entity.Parameters.ToArray());
            var list = Tools.IDataReaderToIlist<T>(sdr);
            //var dt = DbHelper.ExecuteTable(this.Entity.SqlStr, this.Entity.Parameters.ToArray());
            //var list = Tools.DataTableToList<T>(dt);

            var runTime = sw.ElapsedMilliseconds;
            sw.Stop();

            var values = JsonConvert.SerializeObject(this.Entity.Parameters.Select(ex => new { ex.Value, ex.Name }).ToList());
            var result = new LambdaToSql.EntityModel.Result<IList<T>>()
            {
                Data = list,
                ResultValue = 1,
                Method = System.Reflection.MethodBase.GetCurrentMethod().Name,
                RunTime = (int)runTime,
                DeBug = string.Format("执行语句:{0}<br />值:{1}<br />执行时间:{2}ms<br />执行结果:{3}", this.Entity.SqlStr, values, runTime, 1)
            };
            this.Debug = result;
            return result;
        }
        #endregion
    }
}
